const limHelp = {

	/**
	 * 二进制转JSON
	 * @param {ArrayBuffer} buffer 二进制
	 */
	arrayBufferToJson(arr) {
		let str = this.arrayBufferToString(arr);
		return JSON.parse(str);
	},

	/**
	 * 二进制转字符串
	 * @param {ArrayBuffer} buffer 二进制
	 */
	arrayBufferToString(arr) {
		if (typeof arr === 'string') {
			return arr;
		}
		var dataview = new DataView(arr);
		var ints = new Uint8Array(arr.byteLength);
		for (var i = 0; i < ints.length; i++) {
			ints[i] = dataview.getUint8(i);
		}
		arr = ints;
		var str = '',
			_arr = arr;
		for (var i = 0; i < _arr.length; i++) {
			var one = _arr[i].toString(2),
				v = one.match(/^1+?(?=0)/);
			if (v && one.length == 8) {
				var bytesLength = v[0].length;
				var store = _arr[i].toString(2).slice(7 - bytesLength);
				for (var st = 1; st < bytesLength; st++) {
					store += _arr[st + i].toString(2).slice(2);
				}
				str += String.fromCharCode(parseInt(store, 2));
				i += bytesLength - 1;
			} else {
				str += String.fromCharCode(_arr[i]);
			}
		}
		return str;
	},

	/**
	 * json 转ArrayBuffer
	 * @param {Object} json json数据
	 */
	jsonToArrayBuffer(json) {
		return this.stringToArrayBuffer(JSON.stringify(json));
	},

	/**
	 * 字符串转ArrayBuffer
	 * @param {Object} str 字符串
	 */
	stringToArrayBuffer(str) {
		var bytes = new Array();
		var len, c;
		len = str.length;
		for (var i = 0; i < len; i++) {
			c = str.charCodeAt(i);
			if (c >= 0x010000 && c <= 0x10FFFF) {
				bytes.push(((c >> 18) & 0x07) | 0xF0);
				bytes.push(((c >> 12) & 0x3F) | 0x80);
				bytes.push(((c >> 6) & 0x3F) | 0x80);
				bytes.push((c & 0x3F) | 0x80);
			} else if (c >= 0x000800 && c <= 0x00FFFF) {
				bytes.push(((c >> 12) & 0x0F) | 0xE0);
				bytes.push(((c >> 6) & 0x3F) | 0x80);
				bytes.push((c & 0x3F) | 0x80);
			} else if (c >= 0x000080 && c <= 0x0007FF) {
				bytes.push(((c >> 6) & 0x1F) | 0xC0);
				bytes.push((c & 0x3F) | 0x80);
			} else {
				bytes.push(c & 0xFF);
			}
		}
		var array = new Int8Array(bytes.length);
		for (var i = 0; i <= bytes.length; i++) {
			array[i] = bytes[i];
		}
		return array.buffer;
	}
}

class Lim {

	/**
	 * 事件
	 */
	static EVENT = {
		/**
		 * 连接成功（指登录成功）
		 */
		CONNECT: "CONNECT",
		/**
		 * 断开连接
		 */
		DICONNECT: "DICONNECT",
		/**
		 * 收到消息事件
		 */
		MESSAGE_RECEIVED: "MESSAGE_RECEIVED",
		/**
		 * 好友申请事件
		 */
		FRIENDS_APPLY: "FRIENDS_APPLY",
		/**
		 * 好友申请结果事件
		 */
		FRIENDS_APPLY_RESULT: "FRIENDS_APPLY_RESULT",
		/**
		 * 群申请事件
		 */
		GROUP_APPLY: "GROUP_APPLY",
		/**
		 * 群申请结果事件
		 */
		GROUP_APPLY_RESULT: "GROUP_APPLY_RESULT",

		/**
		 * 群解散事件
		 */
		GROUP_DISSOLVE: "GROUP_DISSOLVE",
		/**
		 * 会话列表刷新事件
		 */
		CONVERSATION_LIST_REFRESH: "CONVERSATION_LIST_REFRESH",
		/**
		 * 群组列表事件
		 */
		GROUP_LIST_REFRESH: "GROUP_LIST_REFRESH",
		/**
		 * 好友列表事件
		 */
		FRIENDS_LIST_REFRESH: "FRIENDS_LIST_REFRESH"
	}

	/**
	 * 命令
	 */
	static COM = {
		CONNECT: 0,
		DICONNECT: 1,
		HEARTBEAT: 2,
		EVENT: 10
	}

	constructor() {
		this.magic = 21544914;
		this.version = 1;
		this.contentType = 0;
		this.socket = null;
		this.connect = false;
		this.url;
		this.idleInterval;
		this.event = {};
		//方法绑定，避免this指针问题
		// this.login = this.login.bind(this);
		// this.sendMessage = this.sendMessage.bind(this);
	}

	/**
	 * 创建协议交互命令 （使用UTF-8编码）
	 * @param {type} com	命令 
	 * @param {Object} message 消息体
	 */
	createProtocol(com, message) {
		let res = new ArrayBuffer(0);
		if (message) {
			if (typeof message === 'string') {
				res = limHelp.stringToArrayBuffer(message);
			} else {
				res = limHelp.jsonToArrayBuffer(message)
			}
		}
		let arrayBuffer = new ArrayBuffer(res.byteLength + 11);
		let buffer = new DataView(arrayBuffer);
		//魔数
		buffer.setInt32(0, this.magic);
		//版本号
		buffer.setUint8(4, this.version);
		//数据类型
		buffer.setUint8(5, this.contentType);
		//命令
		buffer.setUint8(6, com);
		//数据长度
		buffer.setInt32(7, res.byteLength);
		//循环填充数据体
		let dataView = new DataView(res);
		for (let i = 0; i < res.byteLength; i++) {
			buffer.setInt8(11 + i, dataView.getInt8(i));
		}
		return arrayBuffer;
	}

	/**
	 * 创建消息JSON类型
	 */
	createMessage() {
		return {
			"messageId": uni.$u.guid()
		};
	}

	/**
	 * 创建登录消息
	 * @param {string} u	用户名
	 * @param {string} p	密码
	 */
	createLoginMessage(u, p) {
		let message = this.createMessage();
		message.u = u;
		message.p = p;
		return this.createProtocol(Lim.COM.CONNECT, message);
	}

	/**
	 * 登录（连接）
	 * @param {string} url 服务端接口
	 * @param {string} u 用户名
	 * @param {string} p 密码
	 */
	login(url, u, p) {
		this.url = url;
		this.socket = uni.connectSocket({
			url: url,
			protocols: ['IM'],
			complete: (res) => {
				if (res.errMsg === 'connectSocket:ok') {
					this.resetIdleInterval();
				}
			}
		});
		this.socket.onOpen(() => {
			//当连接成功的时候，发送登录请求
			let message = this.createLoginMessage(u, p);
			this.socket.send({
				data: message,
				success: () => {
					this.connect = true;
					let func = this.event[Lim.EVENT.CONNECT];
					if (func) {
						func();
					}
				}
			});
		})

		this.socket.onClose((data) => {
			console.log("连接关闭");
			this.clearIdleInterval();
			this.connect = false;
			let func = this.event[Lim.EVENT.DICONNECT];
			if (func) {
				func();
			}
		})

		/**
		 * 监听收到的消息
		 */
		this.socket.onMessage((data) => {
			let r = data.data;
			if (r.byteLength > 4) {
				let magic = new DataView(r.slice(0, 4));
				if (magic.getInt32() != this.magic) {
					return;
				}
			}
			let com = new DataView(r.slice(6, 7));
			let message = limHelp.arrayBufferToJson(r.slice(11));
			if (com.getInt8() === Lim.COM.EVENT) {
				let func = this.event[message.eventName];
				if (func) {
					func(message);
				}
			}
		})
	}

	/**
	 * 重置心跳定时器
	 */
	resetIdleInterval() {
		this.idleInterval = setInterval((than) => {
			if (than.connect) {
				than.sendMessage(than.createProtocol(Lim.COM.HEARTBEAT));
			}
		}, 1000 * 25, this);
	}

	/**
	 * 移除心跳定时器
	 */
	clearIdleInterval() {
		if (this.idleInterval) {
			clearInterval(this.idleInterval);
		}
	}

	/**
	 * 退出（关闭连接）
	 */
	logout() {
		return this.sendMessage(this.createProtocol(Lim.COM.DICONNECT));
	}

	/**
	 * 发送消息
	 * @param {Object} message 消息体
	 */
	sendMessage(message) {
		return new Promise((resolve, reject) => {
			if (this.connect) {
				return this.socket.send({
					data: message,
					success: (res) => {
						resolve(res);
					},
					fail: (err) => {
						reject("连接未打开");
					}
				})
			}
			reject("连接未打开");
		});
	}

	/**
	 * 监听事件
	 * @param {Object} event	事件
	 * @param {Object} f	回调函数
	 */
	on(event, f) {
		this.event[event] = f;
	}

}

const LIM = new Lim();

export default LIM;
